/**
 * 
 */
package cs236369.webserver;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * @author Ady Abraham
 *
 */
public class WebServerConfig implements IWebServerConfig {

	private Document mConfigDoc;
	private String mBaseDirectory;
	private HashMap<String, String> mMimeMapping;
	private int mNumRequestHandlers;
	private int mNumSocketReaders;
	private int mPort;
	private int mRequestQueueSize;
	private int mSocketQueueSize;
	private HashMap<String, ITypeHandler> mTypeHandlers;
	private String mWelcomeFile;
	
	public WebServerConfig(InputStream config) throws IOException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
		DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
		docFactory.setNamespaceAware(true);
		DocumentBuilder builder;
		try {
			builder = docFactory.newDocumentBuilder();
			mConfigDoc = builder.parse(config);
		} catch (SAXException e) {
			System.err.println("ERROR: SAX Parser configuration error: " + e.getMessage());
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			System.err.println("ERROR: Parser configuration error: " + e.getMessage());
			e.printStackTrace();
		}
		
		mConfigDoc.normalize();
		
		mBaseDirectory = getBaseDirectoryInternal();
		mMimeMapping = getMimeMappingInternal();
		mNumRequestHandlers = getNumRequestHandlersInternal();
		mNumSocketReaders = getNumSocketReadersInternal();
		mPort = getPortInternal();
		mRequestQueueSize = getRequestQueueSizeInternal();
		mSocketQueueSize = getSocketQueueSizeInternal();
		mTypeHandlers = getTypeHandlersInternal();
		mWelcomeFile = getWelcomeFileInternal();
	}
	
	
	
	/* (non-Javadoc)
	 * @see cs236369.webserver.IWebServerConfig#getBaseDirectory()
	 */
	@Override
	public String getBaseDirectory() {
		return mBaseDirectory;
	}
	
	private String getBaseDirectoryInternal() {
		return mConfigDoc.getElementsByTagName("server-config").item(0)
		       .getAttributes().getNamedItem("base").getNodeValue();
	}

	/* (non-Javadoc)
	 * @see cs236369.webserver.IWebServerConfig#getMimeMapping()
	 */
	@Override
	public HashMap<String, String> getMimeMapping() {
		return mMimeMapping;	
	}
	
	private HashMap<String, String> getMimeMappingInternal() {
		HashMap<String, String> mimeMap = new HashMap<String, String>();
		
		NodeList mimes = mConfigDoc.getElementsByTagName("mime").item(0).getChildNodes();
		for(int i = 0; i < mimes.getLength(); i++) {
			if (!mimes.item(i).getNodeName().equals("mime-mapping")) {
				continue;
			}
			
			NodeList mimeTypeNodes = mimes.item(i).getChildNodes();
			String extension = null;
			String mimeType = null;
			for(int j = 0; j < mimeTypeNodes.getLength(); j++) {
				if (mimeTypeNodes.item(j).getNodeName().equals("extension")) {
					extension = mimeTypeNodes.item(j).getFirstChild().getNodeValue();
				
				}
				else if (mimeTypeNodes.item(j).getNodeName().equals("mime-type")) {
					mimeType = mimeTypeNodes.item(j).getFirstChild().getNodeValue();
				}
			}
			
			mimeMap.put(extension, mimeType);
		}
		
		return mimeMap;
		
		
		
	}

	/* (non-Javadoc)
	 * @see cs236369.webserver.IWebServerConfig#getNumRequestHandlers()
	 */
	@Override
	public int getNumRequestHandlers() {
		return mNumRequestHandlers; 
	}
	
	private int getNumRequestHandlersInternal() {
		NodeList nodeList = mConfigDoc.getElementsByTagName("request-handlers").item(0).getChildNodes();
		for(int i = 0; i < nodeList.getLength(); i++) {
			if (nodeList.item(i).getNodeName().equals("multi")) {
				String numRequestHandlers = nodeList.item(i).getFirstChild().getNodeValue();
				return Integer.parseInt(numRequestHandlers);
			}
		}
		return -1;
	}

	/* (non-Javadoc)
	 * @see cs236369.webserver.IWebServerConfig#getNumSocketReaders()
	 */
	@Override
	public int getNumSocketReaders() {
		return mNumSocketReaders;
	}
	
	private int getNumSocketReadersInternal() {
		NodeList nodeList = mConfigDoc.getElementsByTagName("socket-readers").item(0).getChildNodes();
		for(int i = 0; i < nodeList.getLength(); i++) {
			if (nodeList.item(i).getNodeName().equals("multi")) {
				String numSocketReaders = nodeList.item(i).getFirstChild().getNodeValue();
				return Integer.parseInt(numSocketReaders);
			}
		}
		return -1;
	}

	/* (non-Javadoc)
	 * @see cs236369.webserver.IWebServerConfig#getPort()
	 */
	@Override
	public int getPort() {
		return mPort;
	}
	
	private int getPortInternal() {
		String port = mConfigDoc.getElementsByTagName("server-config").item(0)
	       .getAttributes().getNamedItem("port").getNodeValue();
		
		return Integer.parseInt(port);
	}

	/* (non-Javadoc)
	 * @see cs236369.webserver.IWebServerConfig#getRequestQueueSize()
	 */
	@Override
	public int getRequestQueueSize() {
		return mRequestQueueSize;
	}
	
	private int getRequestQueueSizeInternal() {
		NodeList nodeList = mConfigDoc.getElementsByTagName("request-queue").item(0).getChildNodes();
		for(int i = 0; i < nodeList.getLength(); i++) {
			if (nodeList.item(i).getNodeName().equals("size")) {
				String requestQueueSize = nodeList.item(i).getFirstChild().getNodeValue();
				return Integer.parseInt(requestQueueSize);
			}
		}
		return -1;
	}

	/* (non-Javadoc)
	 * @see cs236369.webserver.IWebServerConfig#getSocketQueueSize()
	 */
	@Override
	public int getSocketQueueSize() {
		return mSocketQueueSize;

	}

	private int getSocketQueueSizeInternal() {
		NodeList nodeList = mConfigDoc.getElementsByTagName("socket-queue").item(0).getChildNodes();
		for(int i = 0; i < nodeList.getLength(); i++) {
			if (nodeList.item(i).getNodeName().equals("size")) {
				String requestQueueSize = nodeList.item(i).getFirstChild().getNodeValue();
				return Integer.parseInt(requestQueueSize);
			}
		}
		return -1;

	}

		
	@Override
	public HashMap<String, ITypeHandler> getTypeHandlers() throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
		return mTypeHandlers;		
	}
	
	private HashMap<String, ITypeHandler> getTypeHandlersInternal() throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
		HashMap<String, ITypeHandler> typeHandlers = new HashMap<String, ITypeHandler>();
		
		NodeList typeHandlersNodes = mConfigDoc.getElementsByTagName("type-handlers").item(0).getChildNodes();
		for(int i = 0; i < typeHandlersNodes.getLength(); i++) {
			if (!typeHandlersNodes.item(i).getNodeName().equals("type-handler")) {
				continue;
			}
			
			String aClass = typeHandlersNodes.item(i).getAttributes().getNamedItem("class").getNodeValue();
			NodeList parametersAndExtensionsNodes = typeHandlersNodes.item(i).getChildNodes();
			
			LinkedList<String> fileExtensions = new LinkedList<String>(); 
			Properties parameters = new Properties();
			
			for(int j = 0; j < parametersAndExtensionsNodes.getLength(); j++) {
				Node currentNode = parametersAndExtensionsNodes.item(j);
				if (currentNode.getNodeName().equals("parameter")) {
					String name = currentNode.getAttributes().getNamedItem("name").getNodeValue();
					String value = currentNode.getAttributes().getNamedItem("value").getNodeValue();
					parameters.put(name, value);
				}
				else if (currentNode.getNodeName().equals("extension")){
					fileExtensions.add(currentNode.getFirstChild().getNodeValue());					
				}
			}
			
			ITypeHandler typeHandler = (ITypeHandler)Class.forName(aClass).getConstructor(Properties.class).newInstance(parameters);
			
			for(String ext : fileExtensions) {
				typeHandlers.put(ext, typeHandler);
			}
			
		}
		
		return typeHandlers;
		
	}
	
	
	/* (non-Javadoc)
	 * @see cs236369.webserver.IWebServerConfig#getWelcomeFile()
	 */
	@Override
	public String getWelcomeFile() {
		return mWelcomeFile;
	}
	
	private String getWelcomeFileInternal() {
		NodeList nodeList = mConfigDoc.getElementsByTagName("welcome-file");
		
		if (nodeList.getLength() == 0) {
			return "";
		}
		
		return nodeList.item(0).getFirstChild().getNodeValue();
	}
		
}
